#include <direct.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include "patches.hpp"

#include "../tdp2/tdp2_visualresources.hpp"
#include "../tdp2/tdp2_string.hpp"
#include "../tdp2/tdp2_linkedlist.hpp"
#include "../tdp2/tdp2_sort.hpp"

int chartMode = 2;

namespace TRES
{
	struct fHandle
	{
		FILE *f;
	};

	static fHandle CreateResFile(const char *name, const char *type)
	{
		char fname[4096];


		strcpy(fname, "res/");
		strcat(fname, name);
		strcat(fname, ".");
		strcat(fname, type);

		for(int i=0;fname[i];i++)
		{
			if(fname[i] == '/')
			{
				fname[i] = '\0';
				_mkdir(fname);
				fname[i] = '/';
			}
		}

		fHandle fh;
		fh.f = fopen(fname, "wb");

		if(fh.f == NULL)
		{
			fprintf(stderr, "Couldn't open %s", fname);
			exit(-1);
		}

		return fh;
	}

	static void CloseResFile(fHandle f)
	{
		fclose(f.f);
	}

	static void WriteUInt32(fHandle f, unsigned long i)
	{
		unsigned char bytes[4];
		bytes[0] = (unsigned char)((i >> 24) & 255);
		bytes[1] = (unsigned char)((i >> 16) & 255);
		bytes[2] = (unsigned char)((i >> 8) & 255);
		bytes[3] = (unsigned char)((i >> 0) & 255);
		fwrite(bytes, 1, 4, f.f);
	}

	static void WriteUInt16(fHandle f, unsigned short i)
	{
		unsigned char bytes[2];
		bytes[0] = (unsigned char)((i >> 8) & 255);
		bytes[1] = (unsigned char)((i >> 0) & 255);
		fwrite(bytes, 1, 2, f.f);
	}

	static void WriteSInt32(fHandle f, long i)
	{
		WriteUInt32(f, (unsigned long)i);
	}

	static void WriteSInt16(fHandle f, short i)
	{
		WriteSInt32(f, (unsigned long)i);
	}

	static void WriteByte(fHandle f, unsigned char b)
	{
		fwrite(&b, 1, 1, f.f);
	}
	
	static void WriteBool(fHandle f, bool b)
	{
		WriteByte(f, b ? 1 : 0);
	}

	static void WriteString(fHandle f, const char *str)
	{
		if(str == NULL)
			WriteUInt32(f, 0xffffffff);
		else
		{
			unsigned long slen = strlen(str);
			WriteUInt32(f, slen);
			fwrite(str, 1, slen, f.f);
		}
	}

	static void WriteFloat(fHandle f, float flt)
	{
		union
		{
			float f;
			unsigned long i;
		} u;
		u.f = flt;
		WriteUInt32(f, u.i);
	}

	static void WriteResDef(fHandle f, const char *resName, const char *resType, unsigned long count)
	{
		WriteString(f, resName);
		WriteString(f, resType);
		WriteUInt32(f, count);
	}
};



namespace BSP2TMRF
{
	using namespace TDP;

	static void mde_assert(bool condition)
	{
		if(!condition)
		{
			fprintf(stderr, "Malformed BSP data");
			exit(-1);
		}
	}

	// Reads in data to native byte order
	static unsigned int BSPSwapInteger(unsigned int i)
	{
		union
		{
			unsigned char bytes[4];
			unsigned int i;
		} u;

		u.i = i;
		return u.bytes[0] | (u.bytes[1] << 8) | (u.bytes[2] << 16) | (u.bytes[3] << 24);
	}

	static int BSPSwapInteger(int i)
	{
		return (int)BSPSwapInteger((unsigned int)i);
	}

	static float BSPSwapFloat(float f)
	{
		union
		{
			unsigned char bytes[4];
			unsigned int i;
			float f;
		} u;

		u.f = f;
		u.i = u.bytes[0] | (u.bytes[1] << 8) | (u.bytes[2] << 16) | (u.bytes[3] << 24);
		return u.f;
	}

	static void BSPSwapInPlace(unsigned int &i)
	{
		i = BSPSwapInteger(i);
	}

	static void BSPSwapInPlace(int &i)
	{
		i = BSPSwapInteger((unsigned int)i);
	}

	static void BSPSwapInPlace(float &f)
	{
		f = BSPSwapFloat(f);
	}

	static void BSPSwapInPlace3(float *f)
	{
		f[0] = BSPSwapFloat(f[0]);
		f[1] = BSPSwapFloat(f[1]);
		f[2] = BSPSwapFloat(f[2]);
	}

	static void BSPSwapInPlace3(int *i)
	{
		i[0] = BSPSwapInteger(i[0]);
		i[1] = BSPSwapInteger(i[1]);
		i[2] = BSPSwapInteger(i[2]);
	}

	struct BSPBatchSegment
	{
		unsigned int numVerts;
		unsigned int numTris;

		FVec3 mins, maxs;

		bool planeSideCull;			// true = this surface can be plane culled
		FPlane3 plane;

		// lodControl = Only display if current LOD >= lodMin
		// lodHasMax = Only display if LOD < lodMax
		bool lodControl;
		bool lodHasMax;
		float lodMin;
		float lodMax;
		FVec3 lodOrigin;

		int lightMapIndex;			// -1 = vertex lighting
		int volumetricEffectIndex;	// -1 = none

		// Original face index, used for visibility
		unsigned int originalFaceIndex;

		unsigned int originalChartIndex;

		// true if this surface does not intersect any preset volumetric effects
		// (i.e. doesn't need to do per-pixel clipping)
		bool clippedToVolumetrics;

		FVec3 firstVert;	// For plane side cull
		struct BSPBatchGroup *batchGroup;
	};

	struct BSPBatchGroup
	{
		Vector<BSPCommonVert> vertValues;
		Vector<unsigned short> elementValues;

		int materialIndex;

		Vector<BSPBatchSegment> segments;
	};


	class CompiledBSPAsset
	{
	public:
		struct BSPPlane
		{
			FVec3 normal;
			Float32 dist;
		};

		
		struct BSPNode
		{
			unsigned int planeIndex;
			int children[2];	// Negative numbers are leafs, -(leaf+1)
		};

		struct BSPLeaf
		{
			int cluster;
			int area;
			int zone;
			FVec3 mins;
			FVec3 maxs;
			const unsigned int *leafFaces;
			unsigned int numLeafFaces;

			const unsigned int *leafBrushes;
			unsigned int numBrushes;
		};

		struct BSPVolumetricEffect
		{
			Vector<unsigned int> boundingPlanes;
			int surfacePlane;
			FVec3 fogColor;
			float fogDensityScalar;
			unsigned int sceneVolume;
			float transitionDepth;
		};

		struct BSPLightVolume
		{
			unsigned char shCoefs[9][3];
			unsigned char mult;
		};

		struct BSPMapScene
		{
			int			target;
			int			visibleInVolume;
			int			drawInVolume;
			BSPPlane	plane;
			int			sceneFlags;
		};

		struct BSPMapSceneLink
		{
			unsigned int		faceNum;
			unsigned int		sceneNum;
		};

		struct BSPLight
		{
			int			mode;
			FVec3		origin;
			FVec3		color;

			FVec3		matrix[3];
			float		radius;

			CString	maskTextureName;
		};

		struct BSPSubModel
		{
			LinkedList<BSPBatchGroup> batchGroups;
		};

		struct BSPZonePortal
		{
			UInt32 zones[2];
			UInt32 planeIndex;
			Vector<FVec3> windingPoints;
		};

		struct BSPZone
		{
			bool reversePortals;
			Vector<UInt32> zonePortalIndexes;
		};

	protected:
		Vector<int> _materialRemaps;

		Vector<CString> _materialNames;
		Vector<unsigned int> _materialProperties;
		Vector<BSPPlane> _planes;
		Vector<unsigned char> _visibility;
		Vector<BSPVolumetricEffect> _volumetricEffects;
		Vector<BSPSubModel> _models;
		Vector<CString> _lightmapNames;
		Vector<unsigned int> _markFaces;
		Vector<BSPLeaf> _leafs;
		Vector<BSPNode> _nodes;
		Vector<BSPLightVolume> _lightVols;
		Vector<BSPMapScene> _scenes;
		Vector<BSPMapSceneLink> _sceneLinks;
		Vector<BSPLight> _lights;

		Vector<BSPBrush_D> _brushes;
		Vector<unsigned int> _markBrushes;
		Vector<unsigned int> _brushSides;

		Vector<unsigned int> _faceSmoothingGroups;

		Vector<BSPAreaPortal_D> _areaPortals;
		Vector<BSPAreaPortalWinding_D> _areaPortalWindings;
		Vector<BSPAreaPortalPoint_D> _areaPortalPoints;
		Vector<BSPZone> _zones;
		Vector<BSPZonePortal> _zonePortals;

		unsigned int _numClusters;
		unsigned int _visRowWidth;
		unsigned int _numFaces;
		unsigned int _numCharts;

		FVec3 _gridSize;
		FVec3 _gridOrigin;
		unsigned int _gridBounds[3];
		float _gridScalar;

		unsigned int _numSceneIDs;

		void MakeBatchGroupsForMaterial(BSPSubModel *model,
			PatchCalculator *pc,
			const struct BSPModel_D *rawModel,
			unsigned int materialIndex,
			const Vector<struct BSPFace_D> &rawFaces,
			const Vector<struct BSPVertex_D> &rawVerts,
			const Vector<struct BSPIndex_D> &meshVerts);

		void MakeBatchGroupsForModel(BSPSubModel *model,
			const struct BSPModel_D *rawModel,
			const Vector<struct BSPFace_D> &rawFaces,
			const Vector<struct BSPVertex_D> &rawVerts,
			const Vector<struct BSPIndex_D> &meshVerts);

		void MakeBatchGroups(const Vector<struct BSPModel_D> &rawModel,
			const Vector<struct BSPFace_D> &rawFaces,
			const Vector<struct BSPVertex_D> &rawVerts,
			const Vector<struct BSPIndex_D> &meshVerts);

	public:
		void Load(FILE *f, const CString &location);
		void ExportTB(FILE *f);
		void ExportRDX(const char *name, FILE *resFile, FILE *rdxFile);
		void ExportRDXT(const char *name, FILE *manifestFile);
		void ExportSolids(FILE *f);
		void ExportMaterialJobs(FILE *f, const char *shortName);
	};


	bool BSPFaceTypeSupported(int t)
	{
		return t == FaceTypePlanar_k || t == FaceTypeTriMesh_k || t == FaceTypePatch_k;
	}

	void BSPLoadRawResource(void *dest, const Vector<unsigned char> &src, const BSPLump_D *lump)
	{
		if(lump->offset > src.Count() ||
			src.Count() - lump->offset > lump->length)
			mde_assert(false);

		memcpy(dest, src + lump->offset, lump->length);
	}

	template<class _T>
	void BSPLoadResource(Vector<_T> &dest, const Vector<unsigned char> &src, const BSPLump_D *lump)
	{
		if(lump->offset > src.Count() ||
			src.Count() - lump->offset < lump->length ||
			lump->length % sizeof(_T))
		{
			unsigned int i = sizeof(_T);
			mde_assert(false);
		}

		unsigned int count = lump->length / sizeof(_T);
		_T *destPtr;

		dest.Resize(NULL, count);
		memcpy(dest, src + lump->offset, lump->length);

		destPtr = dest;
		while(count)
		{
			destPtr->Swap();
			count--;
		}
	}


	// FIXME
	typedef const BSPFace_D *BSPFace_D_constp;

	int BSPFaceSort(const BSPFace_D_constp *pa, const BSPFace_D_constp *pb)
	{
		const BSPFace_D_constp a = *pa;
		const BSPFace_D_constp b = *pb;

		if(a->lightMap != b->lightMap)
			return a->lightMap - b->lightMap;
		if(a->effect != b->effect)
			return a->effect - b->effect;
		return 0;
	}

	// Converts a BSP vertex into a common storage vertex
	void ConvertVerts(const BSPVertex_D *verts, unsigned int numVerts, BSPCommonVert *converted, int lightmapIndex)
	{
		for(unsigned int i=0;i<numVerts;i++)
		{
			const BSPVertex_D *rawVert = verts + i;
			BSPCommonVert &cv = converted[i];

			cv.position = FVec3(rawVert->position);
			cv.svec = FVec3(rawVert->svec);
			cv.texCoords = FVec2(rawVert->texCoords);

			// Rebuild
			FVec3 tvec = FVec3(rawVert->tvec);
			float normalMultiplier = CompressSTVec(FVec3(rawVert->normal), cv.svec, tvec);
			cv.tvecN = tvec.Join(normalMultiplier);

			if(lightmapIndex >= 0)
			{
				cv.lightmapTexCoords[0] = rawVert->lightMapTexCoords[0];
				cv.lightmapTexCoords[1] = rawVert->lightMapTexCoords[1];
			}
			else
				cv.lightmapTexCoords = FVec2(0.0f);

			converted[i] = cv;
		}
	}

	BSPBatchSegment *PushGeometryToBatchGroup(CompiledBSPAsset::BSPSubModel *model,
		const BSPCommonVert *verts,
		unsigned int numVerts,
		const unsigned int *elements,
		unsigned int numElements,
		int materialIndex,
		int lightmapIndex)
	{
		BSPBatchGroup *bg = NULL;
		unsigned int maxVerts = 32767;

		if(model->batchGroups.Last())
			bg = model->batchGroups.Last()->DataPtr();

		if(bg)
		{
			if(maxVerts - bg->vertValues.Count() < numVerts ||
				bg->materialIndex != materialIndex)
				bg = NULL;	// Overflow
		}

		if(!bg)
		{
			bg = model->batchGroups.CreateLinkAtEnd()->DataPtr();
			bg->materialIndex = materialIndex;
		}

		unsigned int baseElements = bg->elementValues.Count();
		unsigned int baseVerts = bg->vertValues.Count();

		bg->elementValues.Resize(NULL, baseElements + numElements);
		bg->vertValues.Resize(NULL, baseVerts + numVerts);

		bg->segments.Resize(NULL, bg->segments.Count() + 1);
		BSPBatchSegment *seg = bg->segments + bg->segments.Count() - 1;

		seg->batchGroup = bg;
		seg->numTris = numElements / 3;
		seg->numVerts = numVerts;
		seg->lightMapIndex = lightmapIndex;
		if(lightmapIndex != -1)
			seg->lightMapIndex /= NumLightCoefs_k;

		for(unsigned int i=0;i<numElements;i++)
			bg->elementValues[i + baseElements] = (unsigned short)(baseVerts + elements[i]);

		seg->mins = seg->maxs = FVec3(0.0f, 0.0f, 0.0f);

		for(unsigned int i=0;i<numVerts;i++)
		{
			const BSPCommonVert &cv = verts[i];

			bg->vertValues[i + baseVerts] = cv;

			if(i == 0)
			{
				seg->firstVert = cv.position;
				seg->mins = cv.position;
				seg->maxs = cv.position;
			}
			else
			{
				for(int j=0;j<3;j++)
				{
					if(seg->mins[j] > cv.position[j])
						seg->mins[j] = cv.position[j];
					if(seg->maxs[j] < cv.position[j])
						seg->maxs[j] = cv.position[j];
				}
			}
		}

		return seg;
	}

	// Creates a batch group for one material on a model
	void CompiledBSPAsset::MakeBatchGroupsForMaterial(BSPSubModel *model,
		PatchCalculator *pc,
		const BSPModel_D *rawModel,
		unsigned int materialIndex,
		const Vector<BSPFace_D> &rawFaces,
		const Vector<BSPVertex_D> &rawVerts,
		const Vector<BSPIndex_D> &meshVerts)
	{
		unsigned int i, numSurfaces;
		const BSPFace_D *faces;
		const BSPFace_D *face;
		Vector<unsigned int> elementStorage;
		Vector<BSPCommonVert> vertStorage;
		Vector<const BSPFace_D *> faceList;

		unsigned int numElements;
		unsigned int numVerts;
		unsigned int numPatches;

		faces = rawFaces + rawModel->face;

		// Find out how many faces we can accumulate here
		numSurfaces = numVerts = numElements = numPatches = 0;
		for(i=0;i<rawModel->numFaces;i++)
		{
			face = faces + i;
			if(face->texture == materialIndex &&
				BSPFaceTypeSupported(face->faceType))
			{
				if(face->faceType == FaceTypePatch_k)
				{
					numSurfaces++;
					numPatches++;
				}
				else
				{
					numElements += face->numMeshVerts;
					numVerts += face->numVertexes;
					numSurfaces++;
				}
			}
		}

		// Create initial face list
		faceList.Resize(NULL, numSurfaces);
		numSurfaces = 0;
		for(i=0;i<rawModel->numFaces;i++)
		{
			face = faces + i;
			if(face->texture == materialIndex &&
				BSPFaceTypeSupported(face->faceType))
			{
				faceList[numSurfaces] = face;
				numSurfaces++;
			}
		}

		// Sort the face list
		Sorting::Sort<const BSPFace_D *>(faceList, faceList.Count(), BSPFaceSort);

		// Merge all segments
		for(i=0;i<faceList.Count();i++)
		{
			face = faceList[i];

			BSPBatchSegment *seg;
			for(int lod=0;lod<NumLODLevels_k;lod++)
			{
				if(face->faceType == FaceTypePatch_k)
				{
					//if(lod != 0)
					//	continue;

					// Find the patch eval for this face
					BSPPatchEvaluation *pe = pc->patchEvals;
					while(pe->face != face)
						pe++;

					if(pe->lods[lod].aliasesPrevious)
						continue;	// Alias, ignore

					ComputePatch(pe, rawVerts + face->vertex, lod);

					seg = PushGeometryToBatchGroup(model, pe->verts, pe->verts.Count(),
						pe->tris, pe->tris.Count(), _materialRemaps[materialIndex], face->lightMap);

					// Set LOD parameters
					seg->lodControl = true;
					seg->lodMin = pe->lods[lod].lodMultiplier;
					seg->lodHasMax = false;
					seg->lodMax = 0.0f;
					seg->lodOrigin = pe->lodOrigin;

					seg->planeSideCull = false;

					// Find the upper LOD bounds for this division spec
					for(int highLod=lod+1;highLod<NumLODLevels_k;highLod++)
					{
						if(!pe->lods[highLod].aliasesPrevious)
						{
							seg->lodHasMax = true;
							seg->lodMax = pe->lods[highLod].lodMultiplier;
							break;
						}
					}
				}
				else
				{
					if(lod != 0)
						break;	// Brush geometry only has one LOD

					Vector<BSPCommonVert> convertedVerts;
					convertedVerts.Resize(NULL, face->numVertexes);

					ConvertVerts(rawVerts + face->vertex, face->numVertexes, convertedVerts, face->lightMap);

					seg = PushGeometryToBatchGroup(model, convertedVerts, face->numVertexes,
						reinterpret_cast<const unsigned int *>(meshVerts.DataPtr()) + face->meshVert, face->numMeshVerts, _materialRemaps[materialIndex], face->lightMap);

					seg->planeSideCull = true;
					seg->plane.normal = FVec3(face->lightMapMatrix[2]);
					seg->plane.dist = seg->plane.normal.DotProduct(seg->firstVert);
					seg->lodControl = false;
					seg->lodMin = 0.0f;
					seg->lodHasMax = true;
					seg->lodMax = 0.0f;
				}

				if(!seg)
					continue;

				seg->originalFaceIndex = face - rawFaces;
				seg->originalChartIndex = face->smoothingGroup;
				seg->volumetricEffectIndex = face->effect;
				seg->clippedToVolumetrics = (face->faceType == FaceTypePlanar_k);
			}
		}
	}

	// Creates batch groups for all models
	void CompiledBSPAsset::MakeBatchGroupsForModel(BSPSubModel *model,
		const BSPModel_D *rawModel,
		const Vector<BSPFace_D> &rawFaces,
		const Vector<BSPVertex_D> &rawVerts,
		const Vector<BSPIndex_D> &meshVerts)
	{
		// See how many unique materials exist for this model
		unsigned int i, j;
		Vector<unsigned int> materialIndexes;
		const BSPFace_D *face = rawFaces + rawModel->face;
		PatchCalculator patchCalc;

		CalcPatchesForModel(&patchCalc, rawModel, rawFaces, rawVerts, meshVerts);

		for(i=0;i<rawModel->numFaces;i++)
		{
			for(j=0;j<materialIndexes.Count();j++)
				if(materialIndexes[j] == face->texture)
					break;
			if(j == materialIndexes.Count() && (_materialRemaps[face->texture] != -1) &&
				BSPFaceTypeSupported(face->faceType))
			{
				materialIndexes.Resize(NULL, materialIndexes.Count() + 1);
				materialIndexes[materialIndexes.Count() - 1] = face->texture;
			}
			face++;
		}

		// Make batch groups
		for(i=0;i<materialIndexes.Count();i++)
		{
			MakeBatchGroupsForMaterial(model, &patchCalc,
				rawModel, materialIndexes[i], rawFaces, rawVerts, meshVerts);
		}
	}

	void CompiledBSPAsset::MakeBatchGroups(const Vector<BSPModel_D> &rawModels,
		const Vector<BSPFace_D> &rawFaces,
		const Vector<BSPVertex_D> &rawVerts,
		const Vector<BSPIndex_D> &meshVerts)
	{

		unsigned int i;

		_models.Resize(NULL, rawModels.Count());

		for(i=0;i<rawModels.Count();i++)
			MakeBatchGroupsForModel(_models + i, rawModels + i, rawFaces, rawVerts, meshVerts);


		// Materials
		// Batch groups
		// Things that break batch groups:
		// - Different model
		// - Different material
		// - Too many vertices

		// Batch group sorting criteria:
		// - Same lighting method
		// - Same lightmap
		// - Same volumetric effect

		// STATE CHANGE AVOIDANCE:
		// - Surfaces with different volumetric effect states can be merged
		//   if the camera is inside the effect volume.
		// - Surfaces with different baked lighting can be merged for
		//   techniques other than the base technique.


		// *** PROFILE LEVEL BREAKS HERE ***
	}


	void CompiledBSPAsset::Load(FILE *f, const CString &location)
	{
		Vector<unsigned char> mapBytes;
		BSPHeader_D header;
		unsigned int i, j;

		// Load the entire file
		fseek(f, 0, SEEK_END);
		mapBytes.Resize(NULL, ftell(f));
		fseek(f, 0, SEEK_SET);
		fread(mapBytes, 1, mapBytes.Count(), f);

		mde_assert(mapBytes.Count() >= sizeof(header));

		// Localize the header
		memcpy(&header, mapBytes, sizeof(header));
		header.magic = BSPSwapInteger(header.magic);
		header.version = BSPSwapInteger(header.version);
		for(i=0;i<BSPLumpCount_k;i++)
		{
			header.lumps[i].length = BSPSwapInteger(header.lumps[i].length);
			header.lumps[i].offset = BSPSwapInteger(header.lumps[i].offset);
		}

		// Make sure the header checks out
		mde_assert(header.magic == 0x50534254);
		mde_assert(header.version == 0x1);

		unsigned int numSourceTextures;

		// Load the textures list
		{
			Vector<BSPTextureInfo_D> dtextures;
			BSPLoadResource<BSPTextureInfo_D>(dtextures, mapBytes, header.lumps + BSPLumpTextures_k);

			numSourceTextures = dtextures.Count();

			_materialNames.Resize(NULL, numSourceTextures);
			_materialProperties.Resize(NULL, numSourceTextures);
			_materialRemaps.Resize(NULL, numSourceTextures);

			int valid = 0;

			for(i=0;i<dtextures.Count();i++)
			{
				_materialProperties[i] = dtextures[i].contentFlags;

				_materialRemaps[i] = -1;
				if(dtextures[i].surfaceFlags & BSPSurfaceFlagInvisible_k)
					continue;

				_materialRemaps[i] = valid;

				const char *baseName;
				size_t baseNameLen;

				// See how long the name really is so non-NULL-terminated names
				// don't blow up
				baseName = dtextures[i].materialName;
				for(j=0;j<64;j++)
					if(baseName[j] == '\0')
						break;

				_materialNames[valid].Set(NULL, dtextures[i].materialName, j);
				{
					CString checkSub;
					checkSub.SetSubString(NULL, _materialNames[valid], 0, 14);
					if(checkSub == "textures/tdp2_")
					{
						checkSub.SetSubString(NULL, _materialNames[valid], 14);
						_materialNames[valid].SetConcat(NULL, "textures/", checkSub);
					}
				}
				valid++;
			}

			_materialNames.Resize(NULL, valid);
		}

		// Load the planes list
		{
			Vector<BSPPlane_D> dplanes;
			BSPLoadResource<BSPPlane_D>(dplanes, mapBytes, header.lumps + BSPLumpPlanes_k);

			_planes.Resize(NULL, dplanes.Count());

			for(i=0;i<dplanes.Count();i++)
			{
				_planes[i].dist = dplanes[i].dist;
				_planes[i].normal = FVec3(dplanes[i].normal);
			}
		}
		// Load visibility data
		{
			BSPLump_D *lump = header.lumps + BSPLumpVisData_k;
			const unsigned char *src;

			if(lump->length)
			{
				mde_assert(lump->offset <= mapBytes.Count() &&
					mapBytes.Count() - lump->offset >= lump->length &&
					lump->length >= 8);

				src = mapBytes + lump->offset;

				// Read the visibility header
				_numClusters = src[0] | (src[1] << 8) | (src[2] << 16) | (src[3] << 24);
				_visRowWidth = src[4] | (src[5] << 8) | (src[6] << 16) | (src[7] << 24);

				// Make sure everything lines up
				mde_assert(lump->length == 8 + _numClusters * _visRowWidth &&
					_visRowWidth == ((_numClusters + 63) & (~63)) / 8);

				_visibility.ResizeFlat(NULL, lump->length - 8, src + 8);
			}
			else
			{
				_numClusters = 0;
				_visRowWidth = 0;
			}
		}

		// Load raw vertices
		Vector<BSPVertex_D> dRawVerts;
		BSPLoadResource<BSPVertex_D>(dRawVerts, mapBytes, header.lumps + BSPLumpVertexes_k);

		// Load raw brush sides
		Vector<BSPBrushSide_D> dRawBrushSides;
		BSPLoadResource<BSPBrushSide_D>(dRawBrushSides, mapBytes, header.lumps + BSPLumpBrushSides_k);

		// Load raw brushes
		BSPLoadResource<BSPBrush_D>(_brushes, mapBytes, header.lumps + BSPLumpBrushes_k);

		// Verify integrity
		for(i=0;i<_brushes.Count();i++)
		{
			const BSPBrush_D *brush = _brushes + i;
			mde_assert(brush->brushside < dRawBrushSides.Count());
			mde_assert(dRawBrushSides.Count() - brush->brushside >= brush->numBrushSides);
			mde_assert(brush->texture < numSourceTextures);
		}

		_brushSides.Resize(NULL, dRawBrushSides.Count());
		for(i=0;i<dRawBrushSides.Count();i++)
			_brushSides[i] = dRawBrushSides[i].plane;

		// Load effects
		Vector<BSPEffect_D> deffects;
		BSPLoadResource<BSPEffect_D>(deffects, mapBytes, header.lumps + BSPLumpEffects_k);

		_volumetricEffects.Resize(NULL, deffects.Count());

		for(i=0;i<deffects.Count();i++)
		{
			mde_assert(deffects[i].brush < _brushes.Count());

			unsigned int numSides = _brushes[deffects[i].brush].numBrushSides;

			const char *baseName;
			size_t baseNameLen;
			const BSPBrushSide_D *sidesList;

			_volumetricEffects[i].fogColor = FVec3(deffects[i].fogColor);
			_volumetricEffects[i].fogDensityScalar = -deffects[i].fogDensityScalar;	// FIXME: Do this at compile time
			_volumetricEffects[i].transitionDepth = 16.0f;

			// See how long the name really is so non-NULL-terminated names
			// don't blow up
			baseName = deffects[i].materialName;
			for(j=0;j<64;j++)
				if(baseName[j] == '\0')
					break;

			// Copy the brush into the volumetric effect
			sidesList = dRawBrushSides + _brushes[deffects[i].brush].brushside;

			_volumetricEffects[i].sceneVolume = deffects[i].sceneVolume;
			int sceneVol = deffects[i].sceneVolume;

			_volumetricEffects[i].boundingPlanes.Resize(NULL, numSides);

			for(j=0;j<numSides;j++)
				_volumetricEffects[i].boundingPlanes[j] = sidesList[j].plane;

			// Determine the surface plane
			if(deffects[i].brushSide == -1)
				_volumetricEffects[i].surfacePlane = -1;
			else
			{
				mde_assert(deffects[i].brushSide >= 0);
				mde_assert(deffects[i].brushSide < numSides);

				_volumetricEffects[i].surfacePlane = _volumetricEffects[i].boundingPlanes[deffects[i].brushSide];
			}
		}

		// Load meshvert indexes
		Vector<BSPIndex_D> dMeshVerts;
		BSPLoadResource<BSPIndex_D>(dMeshVerts, mapBytes, header.lumps + BSPLumpMeshVerts_k);

		// Verify integrity
		for(i=0;i<dMeshVerts.Count();i++)
			mde_assert(dMeshVerts[i].idx < dRawVerts.Count());

		// Load raw scenes
		Vector<BSPScene_D> dRawScenes;
		BSPLoadResource<BSPScene_D>(dRawScenes, mapBytes, header.lumps + BSPLumpScenes_k);

		// Load raw faces
		Vector<BSPFace_D> dRawFaces;
		BSPLoadResource<BSPFace_D>(dRawFaces, mapBytes, header.lumps + BSPLumpFaces_k);

		_numFaces = dRawFaces.Count();
		_numCharts = 0;
		for(i=0;i<dRawFaces.Count();i++)
			if(dRawFaces[i].smoothingGroup >= _numCharts)
				_numCharts = dRawFaces[i].smoothingGroup+1;

		// Verify integrity
		for(i=0;i<dRawFaces.Count();i++)
		{
			const BSPFace_D *face = dRawFaces + i;
			mde_assert(dRawFaces[i].effect == -1 ||
				(dRawFaces[i].effect >= 0 && dRawFaces[i].effect < _volumetricEffects.Count()));
			mde_assert(dRawFaces[i].meshVert <= dMeshVerts.Count() &&
				dMeshVerts.Count() - dRawFaces[i].meshVert >= dRawFaces[i].numMeshVerts);
			mde_assert(dRawFaces[i].vertex <= dRawVerts.Count() &&
				dRawVerts.Count() - dRawFaces[i].vertex >= dRawFaces[i].numVertexes);

			if(face->faceType == FaceTypePlanar_k ||
				face->faceType == FaceTypeTriMesh_k)
			{
				mde_assert(dRawFaces[i].numMeshVerts % 3 == 0);
			}
			else if(face->faceType == FaceTypePatch_k)
			{
				mde_assert(dRawFaces[i].numVertexes ==
					dRawFaces[i].patchDimensions[0] * dRawFaces[i].patchDimensions[1] &&
					(dRawFaces[i].patchDimensions[0] & 1) == 1 &&
					(dRawFaces[i].patchDimensions[1] & 1) == 1);
			}
		}

		_faceSmoothingGroups.Resize(NULL, _numFaces);
		for(unsigned int i=0;i<_numFaces;i++)
			_faceSmoothingGroups[i] = dRawFaces[i].smoothingGroup;

		// Load raw models
		Vector<BSPModel_D> dRawModels;
		BSPLoadResource<BSPModel_D>(dRawModels, mapBytes, header.lumps + BSPLumpModels_k);

		// Verify integrity
		for(i=0;i<dRawModels.Count();i++)
		{
			mde_assert(dRawModels[i].face <= dRawFaces.Count() &&
				dRawFaces.Count() - dRawModels[i].face >= dRawModels[i].numFaces);
		}


		// *** MAKE BATCH GROUPS ***
		MakeBatchGroups(dRawModels, dRawFaces, dRawVerts, dMeshVerts);

		{
			Vector<BSPIndex_D> dRawLeafBrushes;
			BSPLoadResource<BSPIndex_D>(dRawLeafBrushes, mapBytes, header.lumps + BSPLumpLeafBrushes_k);

			// Process and verify integrity
			_markBrushes.Resize(NULL, dRawLeafBrushes.Count());
			for(i=0;i<dRawLeafBrushes.Count();i++)
				_markBrushes[i] = dRawLeafBrushes[i].idx;
		}

		// Load leaf faces
		Vector<BSPIndex_D> dRawLeafFaces;
		BSPLoadResource<BSPIndex_D>(dRawLeafFaces, mapBytes, header.lumps + BSPLumpLeafFaces_k);

		// Process and verify integrity
		_markFaces.Resize(NULL, dRawLeafFaces.Count());
		for(i=0;i<dRawLeafFaces.Count();i++)
		{
			mde_assert(dRawLeafFaces[i].idx < dRawFaces.Count());
			_markFaces[i] = dRawLeafFaces[i].idx;
		}

		// Discard raw data
		dRawLeafFaces.Release();


		// Load leafs
		Vector<BSPLeaf_D> dRawLeafs;
		BSPLoadResource<BSPLeaf_D>(dRawLeafs, mapBytes, header.lumps + BSPLumpLeafs_k);

		// Process and verify integrity
		_leafs.Resize(NULL, dRawLeafs.Count());

		for(i=0;i<_leafs.Count();i++)
		{
			if(_numClusters != 0)
				mde_assert(dRawLeafs[i].cluster == -1 || 
					(unsigned int)dRawLeafs[i].cluster < _numClusters);
			mde_assert(dRawLeafs[i].leafFace <= _markFaces.Count() &&
				_markFaces.Count() - dRawLeafs[i].leafFace >= dRawLeafs[i].numLeafFaces);
			mde_assert(dRawLeafs[i].mins[0] <= dRawLeafs[i].maxs[0]);
			mde_assert(dRawLeafs[i].mins[1] <= dRawLeafs[i].maxs[1]);
			mde_assert(dRawLeafs[i].mins[2] <= dRawLeafs[i].maxs[2]);

			_leafs[i].leafFaces = _markFaces + dRawLeafs[i].leafFace;
			_leafs[i].numLeafFaces = dRawLeafs[i].numLeafFaces;
			_leafs[i].mins = FVec3(static_cast<Float32>(dRawLeafs[i].mins[0]), static_cast<Float32>(dRawLeafs[i].mins[1]), static_cast<Float32>(dRawLeafs[i].mins[2]));
			_leafs[i].maxs = FVec3(static_cast<Float32>(dRawLeafs[i].maxs[0]), static_cast<Float32>(dRawLeafs[i].maxs[1]), static_cast<Float32>(dRawLeafs[i].maxs[2]));
			_leafs[i].cluster = dRawLeafs[i].cluster;
			_leafs[i].area = dRawLeafs[i].area;

			_leafs[i].leafBrushes = _markBrushes + dRawLeafs[i].leafBrush;
			_leafs[i].numBrushes = dRawLeafs[i].numLeafBrushes;
		}

		// Discard
		dRawLeafs.Release();

		// Load nodes
		Vector<BSPNode_D> dRawNodes;
		BSPLoadResource<BSPNode_D>(dRawNodes, mapBytes, header.lumps + BSPLumpNodes_k);

		// Process and verify integrity
		_nodes.Resize(NULL, dRawNodes.Count());

		for(i=0;i<dRawNodes.Count();i++)
		{
			for(j=0;j<2;j++)
			{
				if(dRawNodes[i].children[j] < 0)
				{
					mde_assert(-(dRawNodes[i].children[j]+1) < _leafs.Count());
				}
				else
				{
					mde_assert(dRawNodes[i].children[j] > i);
					mde_assert(dRawNodes[i].children[j] < dRawNodes.Count());
				}
				_nodes[i].children[j] = dRawNodes[i].children[j];
			}
			mde_assert(dRawNodes[i].planeIndex < _planes.Count());
			_nodes[i].planeIndex = dRawNodes[i].planeIndex;
		}

		// Load light volumes
		if(header.lumps[BSPLumpLightVols_k].length)
		{
			Vector<BSPLightVolume_D> dRawLightVols;
			BSPLoadResource<BSPLightVolume_D>(dRawLightVols, mapBytes, header.lumps + BSPLumpLightVols_k);

			_gridSize = FVec3(dRawModels[0].gridSize);

			// Make sure this is the product of the grid bounds
			for(i=0;i<3;i++)
			{
				_gridBounds[i] = dRawModels[0].gridBounds[i];
				mde_assert(_gridBounds[i] != 0);
				mde_assert(_gridSize[i] != 0);
			}
			_gridScalar = dRawModels[0].gridScalar;

			i = dRawLightVols.Count();
			mde_assert(i % _gridBounds[0] == 0);
			i /= _gridBounds[0];
			mde_assert(i % _gridBounds[1] == 0);
			i /= _gridBounds[1];
			mde_assert(i == _gridBounds[2]);

			_lightVols.Resize(NULL, dRawLightVols.Count());
			for(i=0;i<dRawLightVols.Count();i++)
			{
				const BSPLightVolume_D *vol = dRawLightVols + i;
				BSPLightVolume *outVol = _lightVols + i;

				for(int j=0;j<9;j++)
					for(int k=0;k<3;k++)
						outVol->shCoefs[j][k] = vol->shCoefs[j][k];
				outVol->mult = vol->mult;
			}

			_gridOrigin = FVec3(dRawModels[0].gridOrigin);
		}
		else
		{
			_gridBounds[0] = _gridBounds[1] = _gridBounds[2] = 0;
		}

		// Process portals
		BSPLoadResource<BSPAreaPortal_D>(_areaPortals, mapBytes, header.lumps + BSPLumpAreaPortals_k);
		BSPLoadResource<BSPAreaPortalWinding_D>(_areaPortalWindings, mapBytes, header.lumps + BSPLumpAreaPortalWindings_k);
		BSPLoadResource<BSPAreaPortalPoint_D>(_areaPortalPoints, mapBytes, header.lumps + BSPLumpAreaPortalPoints_k);

		// Create zones from areaportals
		{
			unsigned int numZones = 0;
			for(LargeUInt i=0;i<_leafs.Count();i++)
			{
				_leafs[i].zone = _leafs[i].area;
				if(_leafs[i].area != -1 && _leafs[i].area >= numZones)
					numZones = _leafs[i].area + 1;
			}

			_zones.Resize(NULL, numZones + _areaPortals.Count());
			_zonePortals.Resize(NULL, _areaPortalWindings.Count());
			
			// Create zones from existing areas
			for(LargeUInt z=0;z<numZones;z++)
			{
				// Count portals
				LargeUInt numPortals = 0;
				for(LargeUInt j=0;j<_areaPortalWindings.Count();j++)
					if(_areaPortalWindings[j].outsideArea == z)
						numPortals++;
				_zones[z].reversePortals = false;
				_zones[z].zonePortalIndexes.Resize(NULL, numPortals);

				LargeUInt portalIndex = 0;
				for(LargeUInt j=0;j<_areaPortalWindings.Count();j++)
					if(_areaPortalWindings[j].outsideArea == z)
						_zones[z].zonePortalIndexes[portalIndex++] = j;
			}

			for(LargeUInt j=0;j<_areaPortalWindings.Count();j++)
			{
				const BSPAreaPortalWinding_D &w = _areaPortalWindings[j];
				_zonePortals[j].zones[0] = w.outsideArea;
				_zonePortals[j].planeIndex = w.plane;
				_zonePortals[j].windingPoints.Resize(NULL, w.numPoints);
				for(LargeUInt k=0;k<w.numPoints;k++)
					_zonePortals[j].windingPoints[k] = _areaPortalPoints[w.firstPoint + k].point;
			}

			// Create new zones from area portals
			for(LargeUInt ap=0;ap<_areaPortals.Count();ap++)
			{
				LargeUInt zone = numZones+ap;
				_zones[zone].reversePortals = true;
				_zones[zone].zonePortalIndexes.Resize(NULL, _areaPortals[ap].numWindings);
				_leafs[_areaPortals[ap].leaf].zone = zone;

				LargeUInt firstWinding = _areaPortals[ap].firstWinding;
				for(LargeUInt j=0;j<_areaPortals[ap].numWindings;j++)
				{
					_zonePortals[j+firstWinding].zones[1] = zone;
					_zones[zone].zonePortalIndexes[j] = j+firstWinding;
				}
			}
		}


		/*
		BSPLumpEntities_k,
		BSPLumpLightMaps_k,

		BSPLumpNodes_k,

		*/

		{
			unsigned int maxLightMap = 0;
			CString baseName;

			// See how many lightmaps we need
			for(i=0;i<dRawFaces.Count();i++)
			{
				// FIXME: Signed/unsigned mismatch
				if(dRawFaces[i].lightMap >= 0 &&
					dRawFaces[i].lightMap >= maxLightMap)
					maxLightMap = dRawFaces[i].lightMap + NumLightCoefs_k;
			}

			// Load all lightmaps
			unsigned int numLightmaps = maxLightMap/NumLightCoefs_k;
			_lightmapNames.Resize(NULL, numLightmaps);

			baseName.Set(NULL, location);

			for(i=0;i<_lightmapNames.Count();i++)
			{
				CString extendedName;
				CString numString;

				numString.SetToInt(NULL, i);

				// Get the full name
				extendedName.SetConcat(NULL, baseName, "_lm");
				extendedName.Append(NULL, numString);

				_lightmapNames[i].Set(NULL, extendedName);
			}
		}


		_scenes.Resize(NULL, dRawScenes.Count());

		// Verify integrity and process
		for(i=0;i<dRawScenes.Count();i++)
		{
			mde_assert(dRawScenes[i].planeNum == -1 || dRawScenes[i].planeNum < _planes.Count());
			// Nearplane flag requires a valid plane
			mde_assert(dRawScenes[i].planeNum != -1 || !(dRawScenes[i].sceneFlags & SceneFlagNearplane_k));

			_scenes[i].drawInVolume = dRawScenes[i].drawInVolume;
			_scenes[i].plane = _planes[dRawScenes[i].planeNum];
			_scenes[i].target = dRawScenes[i].target;
			_scenes[i].sceneFlags = dRawScenes[i].sceneFlags;
			_scenes[i].visibleInVolume = dRawScenes[i].visibleInVolume;
		}

		// Find scene-linked faces
		for(i=0;i<dRawFaces.Count();i++)
		{
			for(j=0;j<BSPFaceNumLinkedScenes_k;j++)
			{
				if(dRawFaces[i].faceType != FaceTypePlanar_k)
					continue;

				int linkedSceneNum = dRawFaces[i].linkedScenes[j];
				mde_assert(linkedSceneNum == -1 || linkedSceneNum < dRawScenes.Count());

				// Create a scene link
				if(linkedSceneNum != -1)
				{
					_sceneLinks.Resize(NULL, _sceneLinks.Count() + 1);
					_sceneLinks[_sceneLinks.Count() - 1].faceNum = i;
					_sceneLinks[_sceneLinks.Count() - 1].sceneNum = linkedSceneNum;
				}
			}
		}

		// Load RT lights
		{
			Vector<BSPRTLight_D> dRawLights;
			BSPLoadResource<BSPRTLight_D>(dRawLights, mapBytes, header.lumps + BSPLumpRTLights_k);

			_lights.Resize(NULL, dRawLights.Count());
			for(i=0;i<dRawLights.Count();i++)
			{
				_lights[i].mode = dRawLights[i].mode;

				if(_lights[i].mode == LightModeCubeMap_k ||
					_lights[i].mode == LightModeProjected_k)
				{
					// Load the RTLight texture
					CString numString;
					numString.SetToInt(NULL, i);

					_lights[i].maskTextureName.SetConcat(NULL, location, "_rtl_");
					_lights[i].maskTextureName.Append(NULL, numString);
				}

				switch(dRawLights[i].mode)
				{
				case LightModeCubeMap_k:
				case LightModeOmni_k:
					_lights[i].radius = dRawLights[i].parameters[0];
					break;
				};

				_lights[i].origin = FVec3(dRawLights[i].origin);
				_lights[i].color = FVec3(dRawLights[i].color);
			}
		}
	}

	void CompiledBSPAsset::ExportMaterialJobs(FILE *f, const char *shortName)
	{
		fprintf(f, "local dependencies =\n");
		fprintf(f, "{\n");
		fprintf(f, "    \"maps/%s.bsp\",\n", shortName);
		fprintf(f, "    \"generated/maps/%s.resbundle\",\n", shortName);
		fprintf(f, "    \"rdxclasses/TDP/Resources/Maps/%s.rxb\",\n", shortName);
		fprintf(f, "}\n");
		fprintf(f, "local bundle = createResourceBundle()\n");
		fprintf(f, "bundle:addExternalGeometry(\"bundledGeometryData\", \"maps/%s.geom\", \"WorldVertex\", \"Data.bundledClusters\")\n", shortName);
		for(unsigned int i=0;i<_materialNames.Count();i++)
			fprintf(f, "bundle:addMaterial(\"material%i\", compileMaterial(\"%s\"))\n", i, _materialNames[i].Chars());
		for(unsigned int i=0;i<this->_lightmapNames.Count();i++)
		{
			// TODO: Control this using chart mode
			fprintf(f, "compileLightMapTexture(%i, \"maps/%s_lm%i_cm%i\", \"maps/%s/hrlm%04i\", \"maps/%s/lrlm%04i\")\n", chartMode, shortName, i, chartMode, shortName, i, shortName, i);
			switch(chartMode)
			{
			case 2:
				// SuperDeluxe
				fprintf(f, "bundle:addTexture(\"lightmap_%i_b1\", \"maps/%s/hrlm%04i_b1\", true, 0)\n", i, shortName, i);
				fprintf(f, "bundle:addTexture(\"lightmap_%i_b2\", \"maps/%s/hrlm%04i_b2\", true, 0)\n", i, shortName, i);
				fprintf(f, "bundle:addTexture(\"lightmap_%i_b3\", \"maps/%s/hrlm%04i_b3\", true, 0)\n", i, shortName, i);
				fprintf(f, "bundle:addTexture(\"lightmap_%i_sgc\", \"maps/%s/hrlm%04i_sgc\", true, 0)\n", i, shortName, i);
				fprintf(f, "bundle:addTexture(\"lightmap_%i_sgd\", \"maps/%s/hrlm%04i_sgd\", true, 0)\n", i, shortName, i);
				break;
			case 3:
				fprintf(f, "bundle:addTexture(\"lightmap_%i_c\", \"maps/%s/hrlm%04i_c\", true, 0)\n", i, shortName, i);
				fprintf(f, "bundle:addTexture(\"lightmap_%i_lq\", \"maps/%s/hrlm%04i_lq\", true, 0)\n", i, shortName, i);
				fprintf(f, "bundle:addTexture(\"lightmap_%i_lr\", \"maps/%s/lrlm%04i\", true, 0)\n", i, shortName, i);
				break;
			}
		}

		fprintf(f, "bundle:buildResourceCache(\"maps/%s.resbundle\", \"maps/%s.resources.rdx\", { \"TDP\", \"Resources\", \"Maps\", \"%s\" }, dependencies )\n", shortName, shortName, shortName);
		fprintf(f, "writeDependencies(\"maps/%s.assets\", dependencies)\n", shortName);
	}

	/*
	void CompiledBSPAsset::ExportSolids(FILE *f)
	{
		Vector<unsigned int> brushToSolid;
		Vector<bool> brushIsSolid;
		unsigned int numSolidBrushes = 0;

		brushIsSolid.Resize(_brushes.Count());
		brushToSolid.Resize(_brushes.Count());
		for(unsigned int i=0;i<_brushes.Count();i++)
		{
			brushIsSolid[i] = false;
			if(_materialProperties[_brushes[i].texture] & 1)
			{
				brushIsSolid[i] = true;
				brushToSolid[i] = numSolidBrushes++;
			}
		}

		writeres(f, "Solids", NULL, NULL);

		writeuint32(f, numSolidBrushes);
		for(unsigned int i=0;i<_brushes.Count();i++)
		{
			BSPBrush_D *brush = _brushes + i;
			if(brushIsSolid[i])
			{
				writeuint32(f, brush->numBrushSides);

				const unsigned int *bs = _brushSides + brush->brushside;
				for(unsigned int i=0;i<brush->numBrushSides;i++)
					writeuint32(f, bs[i]);
			}
		}

		writeuint32(f, _leafs.Count());

		for(unsigned int i=0;i<_leafs.Count();i++)
		{
			BSPLeaf *leaf = _leafs + i;
			unsigned int numSolidLBs = 0;

			for(unsigned int j=0;j<leaf->numBrushes;j++)
			{
				if(brushIsSolid[leaf->leafBrushes[j]])
					numSolidLBs++;
			}

			writeuint32(f, numSolidLBs);
			for(unsigned int j=0;j<leaf->numBrushes;j++)
			{
				if(brushIsSolid[leaf->leafBrushes[j]])
					writeuint32(f, leaf->leafBrushes[j]);
			}
		}

		writeendres(f);
	}
	*/

	void DecomposeFloat(float f, int &frac, int &x)
	{
		union
		{
			float f;
			int i;
		} u;

		u.f = f;
		if(u.i == 0)
		{
			frac = 0;
			x = 0;
			return;
		}

		int baseFrac = u.i & 0x7FFFFF;
		baseFrac |= 0x800000;
		int baseX = (u.i & 0x7F800000) >> 23;

		while(!(baseFrac & 1))
		{
			baseX++;
			baseFrac >>= 1;
		}

		x = baseX - 150;

		if((u.i & 0x80000000) != 0)
			baseFrac = -baseFrac;
		frac = baseFrac;
	}

	const char *EncFloat(float f)
	{
		static int cycle = 0;
		static char buffers[4][50];

		int frac, x;
		DecomposeFloat(f, frac, x);

		// Try to get rid of the exponent
		{
			bool failed = false;
			while(x > 0)
			{
				if(frac >= 0x4000000 || frac < -0x4000000)
				{
					failed = true;
					break;
				}
				x--;
				frac *= 2;
			}
		}

		if(x != 0)
			sprintf(buffers[cycle], "%i^%i", frac, x);
		else
			sprintf(buffers[cycle], "%i", frac);

		const char *out = buffers[cycle];
		cycle++;
		if(cycle == 4)
			cycle = 0;
		return out;
	}
	
	void StoreFloats(FILE *f, const Float32 *fv, int count)
	{
		while(count)
		{
			fputc(sizeof(Float32), f);
			fwrite(fv, sizeof(Float32), 1, f);

			count--;
			fv ++;
		}
	}

	const char *EncVector(const float *v, int count, bool sourceFormat)
	{
		static int cycle = 0;
		static char buffers[4][250];
		char tempBuf;
		const char *components[] = { "x", "y", "z", "w" };

		buffers[cycle][0] = '\0';

		if(sourceFormat)
			sprintf(buffers[cycle], "new Vec%i ", count);

		strcat(buffers[cycle], "{ ");
		for(int i=0;i<count;i++)
		{
			if(i > 0)
				strcat(buffers[cycle], ", ");
			strcat(buffers[cycle], components[i]);
			if(sourceFormat)
				strcat(buffers[cycle], " = ");
			else
				strcat(buffers[cycle], " : ");
			strcat(buffers[cycle], EncFloat(v[i]));
		}
		strcat(buffers[cycle], " } ");

		const char *out = buffers[cycle];
		cycle++;
		if(cycle == 4)
			cycle = 0;
		return out;
	}

//#define WRITEFLOAT(f, v)	do { int frac, x; DecomposeFloat(v, frac, x); if(x) fprintf(f, " %i^%i", frac, x); else fprintf(f, " %i^%i", frac, x); } while(0)
//#define WRITEVEC(f, v, count)	do { fprintf(f, " {"); for(unsigned int mri=0;mri<count;mri++) { WRITEFLOAT(f, v[mri]); } fprintf(f, " }"); } while (0)

	void CompiledBSPAsset::ExportRDX(const char *mapShortName, FILE *resFile, FILE *rdxFile)
	{
		char fname[4096];
		char ref[4096];
		
		fprintf(rdxFile, "using Core;\n");
		fprintf(rdxFile, "using TDP;\n");
		fprintf(rdxFile, "using TDP.Math;\n");
		fprintf(rdxFile, "using TDP.Resources;\n");
		fprintf(rdxFile, "namespace TDP\n");
		fprintf(rdxFile, "{\n");
		fprintf(rdxFile, "namespace Resources\n");
		fprintf(rdxFile, "{\n");
		fprintf(rdxFile, "namespace Maps\n");
		fprintf(rdxFile, "{\n");
		fprintf(rdxFile, "namespace %s\n", mapShortName);
		fprintf(rdxFile, "{\n");
		fprintf(rdxFile, "public class Data\n");
		fprintf(rdxFile, "{\n");

		fprintf(rdxFile, "public resource WorldModel worldModel = new WorldModel\n", mapShortName);
		fprintf(rdxFile, "{\n");
		fprintf(rdxFile, "    numFaces = %i,\n", _numFaces);
		fprintf(rdxFile, "    numCharts = %i,\n", _numCharts);
		fprintf(rdxFile, "    numLightmaps = %i,\n", _lightmapNames.Count());
		fprintf(rdxFile, "    numScenes = %i,\n", _scenes.Count());
		fprintf(rdxFile, "    numClusters = %i,\n", _numClusters);
		fprintf(rdxFile, "    gridSize = %s,\n", EncVector(_gridSize, 3, true));
		fprintf(rdxFile, "    gridOrigin = %s,\n", EncVector(_gridOrigin, 3, true));
		fprintf(rdxFile, "    gridSizeX = %i,\n", _gridBounds[0]);
		fprintf(rdxFile, "    gridSizeY = %i,\n", _gridBounds[1]);
		fprintf(rdxFile, "    gridSizeZ = %i,\n", _gridBounds[2]);
		fprintf(rdxFile, "    gridScalar = %s,\n", EncFloat(_gridScalar));
		fprintf(rdxFile, "    visRowWidth = %i,\n", _visRowWidth);
		fprintf(rdxFile, "    visibilityData = visibility,\n");
		fprintf(rdxFile, "    faceSmoothingGroups = faceSmoothingGroups,\n");
		fprintf(rdxFile, "    planes = planes,\n");
		fprintf(rdxFile, "    markFaces = markFaces,\n");
		fprintf(rdxFile, "    leafs = leafs,\n");
		fprintf(rdxFile, "    nodes = nodes,\n");
		fprintf(rdxFile, "    models = models,\n");
		fprintf(rdxFile, "    volumetricEffects = volumetricEffects,\n");
		fprintf(rdxFile, "    geometryData = GeneratedResources.bundledGeometryData,\n");
		fprintf(rdxFile, "    zonePortals = zonePortals,\n");
		fprintf(rdxFile, "    zones = zones,\n");
		fprintf(rdxFile, "};\n");

		fprintf(rdxFile, "public anonymous resource int const[] faceSmoothingGroups = new int const[]\n");
		fprintf(rdxFile, "{\n");

		for(unsigned int i=0;i<_faceSmoothingGroups.Count();i++)
		{
			if(i % 20 == 0)
				fprintf(rdxFile, "\n    ");
			fprintf(rdxFile, "    %i,", _faceSmoothingGroups[i]);
		}
		fprintf(rdxFile, "\n};\n");

		fprintf(rdxFile, "public anonymous resource Plane3 const[] planes = new Plane3 const[]\n");
		fprintf(rdxFile, "{\n");
		for(unsigned int i=0;i<_planes.Count();i++)
		{
			fprintf(rdxFile, "    new Plane3 {\n");
			fprintf(rdxFile, "        normal = %s,\n", EncVector(_planes[i].normal, 3, true));
			fprintf(rdxFile, "        dist = %s,\n", EncFloat(_planes[i].dist));
			fprintf(rdxFile, "    },\n");
		}
		fprintf(rdxFile, "};\n");

		fprintf(rdxFile, "public anonymous resource byte const[] visibility = new byte const[]\n");
		fprintf(rdxFile, "{");
		for(unsigned int i=0;i<_visibility.Count();i++)
		{
			if(i % 20 == 0)
				fprintf(rdxFile, "\n    ");
			fprintf(rdxFile, "%i, ", _visibility[i]);
		}
		fprintf(rdxFile, "\n};\n");

		fprintf(rdxFile, "public anonymous resource WorldModel.VolumetricEffect const[] volumetricEffects =\n");
		fprintf(rdxFile, "\tnew WorldModel.VolumetricEffect const[] \n");
		fprintf(rdxFile, "{\n");
		for(unsigned int i=0;i<_volumetricEffects.Count();i++)
		{
			const BSPVolumetricEffect &ve = _volumetricEffects[i];
			fprintf(rdxFile, "    new WorldModel.VolumetricEffect {\n");
			fprintf(rdxFile, "        boundingPlanes = volumetricEffect%i_boundingPlanes,\n", i);
			fprintf(rdxFile, "        surfacePlaneIndex = %i,\n", ve.surfacePlane);
			fprintf(rdxFile, "        fogColor = %s,\n", EncVector(ve.fogColor, 3, true));
			fprintf(rdxFile, "        fogDensityScalar = %s,\n", EncFloat(ve.fogDensityScalar));
			fprintf(rdxFile, "        transitionDepth = %s,\n", EncFloat(ve.transitionDepth));
			fprintf(rdxFile, "        sceneVolume = %i\n", ve.sceneVolume);
			fprintf(rdxFile, "    },\n");
		}
		fprintf(rdxFile, "};\n");

		for(unsigned int i=0;i<_volumetricEffects.Count();i++)
		{
			const BSPVolumetricEffect &ve = _volumetricEffects[i];

			fprintf(rdxFile, "public anonymous resource int const[] volumetricEffect%i.boundingPlanes = new int const[]\n", i);
			fprintf(rdxFile, "{\n");
			for(unsigned int i=0;i<ve.boundingPlanes.Count();i++)
				fprintf(rdxFile, "    %i,\n", ve.boundingPlanes[i]);
			fprintf(rdxFile, "};\n");
		}

		fprintf(rdxFile, "public anonymous resource WorldModel.Model const[] models =\n");
		fprintf(rdxFile, "\tnew WorldModel.Model const[]\n");
		fprintf(rdxFile, "{\n");
		{
			unsigned int firstCluster = 0;
			for(unsigned int i=0;i<_models.Count();i++)
			{
				fprintf(rdxFile, "    new WorldModel.Model {\n");
				fprintf(rdxFile, "        clusterInfos = model%i_clusterInfos,\n", i);
				fprintf(rdxFile, "        firstCluster = %i,\n", firstCluster);
				fprintf(rdxFile, "    },\n");
				firstCluster += _models[i].batchGroups.NumLinks();
			}
		}
		fprintf(rdxFile, "};\n");

		{
			unsigned int linkIndex=0;
			for(unsigned int i=0;i<_models.Count();i++)
			{
				const BSPSubModel &model = _models[i];

				fprintf(rdxFile, "public anonymous resource WorldModel.GeometryClusterInfo const[] model%i_clusterInfos =\n", i);
				fprintf(rdxFile, "\tnew WorldModel.GeometryClusterInfo const[]\n");
				fprintf(rdxFile, "{\n");
				for(const ListLink<BSPBatchGroup> *ll = model.batchGroups.First();
					ll;ll=ll->Next(), linkIndex++)
				{
					fprintf(rdxFile, "    new WorldModel.GeometryClusterInfo {\n");
					fprintf(rdxFile, "        segmentInfos = cluster%i_segments,\n", linkIndex);
					fprintf(rdxFile, "        bundleClusterIndex = %i,\n", linkIndex);
					fprintf(rdxFile, "        material = GeneratedResources.material%i,\n", ll->DataPtr()->materialIndex);
					fprintf(rdxFile, "    },\n");
				}
				fprintf(rdxFile, "};\n");
			}
		}
		
		{
			unsigned int linkIndex=0;

			fprintf(rdxFile, "public anonymous resource BundledGeometryData.Cluster const[] bundledClusters =\n");
			fprintf(rdxFile, "\tnew BundledGeometryData.Cluster const[]\n");
			fprintf(rdxFile, "{\n");
			for(unsigned int i=0;i<_models.Count();i++)
			{
				unsigned int modelIndex = i;
				const BSPSubModel &model = _models[i];

				for(const ListLink<BSPBatchGroup> *ll = model.batchGroups.First();
					ll;ll=ll->Next(), linkIndex++)
				{
					const BSPBatchGroup &bg = ll->Data();

					fprintf(rdxFile, "    new BundledGeometryData.Cluster\n");
					fprintf(rdxFile, "    {\n");
					fprintf(rdxFile, "        segments = new BundledGeometryData.Segment const[]\n");
					fprintf(rdxFile, "        {\n");
					for(unsigned int i=0;i<bg.segments.Count();i++)
					{
						fprintf(rdxFile, "            new BundledGeometryData.Segment\n");
						fprintf(rdxFile, "            {\n");
						fprintf(rdxFile, "                numVerts = %i,\n", bg.segments[i].numVerts);
						fprintf(rdxFile, "                numTris = %i,\n", bg.segments[i].numTris);
						fprintf(rdxFile, "            },\n");
					}
					fprintf(rdxFile, "        },\n");
					fprintf(rdxFile, "    },\n");
					fprintf(rdxFile, "};\n");

					// Write binary data to the resource file
					for(unsigned int i=0;i<bg.vertValues.Count();i++)
					{
						const BSPCommonVert &v = bg.vertValues[i];

						// This must match PackedWorldVertex
						StoreFloats(resFile, v.position, 3);
						StoreFloats(resFile, v.texCoords, 2);
						StoreFloats(resFile, v.lightmapTexCoords, 2);
						StoreFloats(resFile, v.svec, 3);
						StoreFloats(resFile, v.tvecN, 4);

						{
							// Empty rgbShadow info
							static const UInt8 emptyLightingInfo[] = { 1, 0, 1, 0, 1, 0, 1, 0 };
							fwrite(emptyLightingInfo, 8, 1, resFile);
						}
					}

					for(unsigned int i=0;i<bg.elementValues.Count();i++)
					{
						fputc(sizeof(UInt16), resFile);
						fwrite(bg.elementValues + i, sizeof(UInt16), 1, resFile);
					}
				}
			}
		}

		
		{
			unsigned int linkIndex=0;

			for(unsigned int i=0;i<_models.Count();i++)
			{
				unsigned int modelIndex = i;
				const BSPSubModel &model = _models[i];

				for(const ListLink<BSPBatchGroup> *ll = model.batchGroups.First();
					ll;ll=ll->Next(), linkIndex++)
				{
					const BSPBatchGroup &bg = ll->Data();

					fprintf(rdxFile, "public anonymous resource WorldModel.GeometrySegmentInfo const[] cluster%i_segments =\n", linkIndex);
					fprintf(rdxFile, "\tnew WorldModel.GeometrySegmentInfo const[]\n");
					fprintf(rdxFile, "{\n");
					for(unsigned int i=0;i<bg.segments.Count();i++)
					{
						fprintf(rdxFile, "    new WorldModel.GeometrySegmentInfo {\n");
						const BSPBatchSegment &seg = bg.segments[i];

						fprintf(rdxFile, "        numVerts = %i,\n", seg.numVerts);
						fprintf(rdxFile, "        numTris = %i,\n", seg.numTris);
						fprintf(rdxFile, "        bounds = new Bounds3 { mins = %s, maxs = %s },\n", EncVector(seg.mins, 3, true), EncVector(seg.maxs, 3, true));
						fprintf(rdxFile, "        planeSideCull = %s,\n", seg.planeSideCull ? "true" : "false");
						fprintf(rdxFile, "        plane = new Plane3 { normal = %s, dist = %s },\n", EncVector(seg.plane.normal, 3, true), EncFloat(seg.plane.dist));
						fprintf(rdxFile, "        volumetricEffectIndex = %i,\n", seg.volumetricEffectIndex);
						fprintf(rdxFile, "        originalFaceIndex = %i,\n", seg.originalFaceIndex);
						fprintf(rdxFile, "        originalChartIndex = %i,\n", seg.originalChartIndex);

						switch(chartMode)
						{
						case 2:
							fprintf(rdxFile, "        lightMapB1Texture = GeneratedResources.lightmap_%i_b1,\n", seg.lightMapIndex);
							fprintf(rdxFile, "        lightMapB2Texture = GeneratedResources.lightmap_%i_b2,\n", seg.lightMapIndex);
							fprintf(rdxFile, "        lightMapB3Texture = GeneratedResources.lightmap_%i_b3,\n", seg.lightMapIndex);
							fprintf(rdxFile, "        lightMapSGDTexture = GeneratedResources.lightmap_%i_sgd,\n", seg.lightMapIndex);
							fprintf(rdxFile, "        lightMapSGCTexture = GeneratedResources.lightmap_%i_sgc,\n", seg.lightMapIndex);
							break;
						case 3:
							fprintf(rdxFile, "        lightMapCTexture = GeneratedResources.lightmap_%i_c,\n", seg.lightMapIndex);
							fprintf(rdxFile, "        lightMapLQTexture = GeneratedResources.lightmap_%i_lq,\n", seg.lightMapIndex);
							break;
						}

						fprintf(rdxFile, "        lightMapIndex = %i,\n", seg.lightMapIndex);
						fprintf(rdxFile, "        environmentMapTexture = null,\n");
						fprintf(rdxFile, "        clippedToVolumetrics = %s,\n", seg.clippedToVolumetrics ? "true" : "false");
						fprintf(rdxFile, "        lodControl = %s,\n", seg.lodControl ? "true" : "false");

						if(seg.lodControl)
						{
							fprintf(rdxFile, "        lodOrigin = %s,\n", EncVector(seg.lodOrigin, 3, true));
							fprintf(rdxFile, "        lodMin = %s,\n", EncFloat(seg.lodMin));
							fprintf(rdxFile, "        lodHasMax = %s,\n", seg.lodHasMax ? "true" : "false");
							if(seg.lodHasMax)
								fprintf(rdxFile, "        lodMax = %s,\n", EncFloat(seg.lodMax));
						}
						fprintf(rdxFile, "    },\n");
					}

					fprintf(rdxFile, "};\n");
				}
			}
		}

		fprintf(rdxFile, "public anonymous resource int const[] markFaces = new int const[]\n");
		fprintf(rdxFile, "{");
		
		for(unsigned int i=0;i<_markFaces.Count();i++)
		{
			if(i % 20 == 0)
				fprintf(rdxFile, "\n    ");
			fprintf(rdxFile, "%i, ", _markFaces[i]);
		}
		fprintf(rdxFile, "\n};\n");

		fprintf(rdxFile, "public anonymous resource WorldModel.Leaf const[] leafs = new WorldModel.Leaf const[]\n");
		fprintf(rdxFile, "{\n");
		for(unsigned int i=0;i<_leafs.Count();i++)
		{
			const BSPLeaf &leaf = _leafs[i];
			
			fprintf(rdxFile, "    new WorldModel.Leaf {\n");
			fprintf(rdxFile, "        cluster = %i,\n", leaf.cluster);
			fprintf(rdxFile, "        zone = %i,\n", leaf.zone);
			fprintf(rdxFile, "        bounds = new Bounds3 { mins = %s, maxs = %s },\n", EncVector(leaf.mins, 3, true), EncVector(leaf.maxs, 3, true));
			fprintf(rdxFile, "        firstMarkFace = %i,\n", leaf.leafFaces - _markFaces.DataPtr());
			fprintf(rdxFile, "        numMarkFaces = %i,\n", leaf.numLeafFaces);
			fprintf(rdxFile, "    },\n");
		}
		fprintf(rdxFile, "};\n");
		
		fprintf(rdxFile, "public anonymous resource WorldModel.Node const[] nodes = new WorldModel.Node const[]\n");
		fprintf(rdxFile, "{\n");
		for(unsigned int i=0;i<_nodes.Count();i++)
		{
			const BSPNode &node = _nodes[i];
			
			fprintf(rdxFile, "    new WorldModel.Node {\n");
			fprintf(rdxFile, "        planeIndex = %i,\n", node.planeIndex);
			fprintf(rdxFile, "        frontChild = %i,\n", node.children[0]);
			fprintf(rdxFile, "        backChild = %i,\n", node.children[1]);
			fprintf(rdxFile, "    },\n");
		}
		fprintf(rdxFile, "};\n");

				
		for(unsigned int i=0;i<_zones.Count();i++)
		{
			const BSPZone &zone = _zones[i];
			
			fprintf(rdxFile, "public anonymous resource int const[] zone%i_portalIndexes = new int const[]\n", i);
			fprintf(rdxFile, "{\n");

			for(unsigned int j=0;j<zone.zonePortalIndexes.Count();j++)
				fprintf(rdxFile, "    %i,\n", zone.zonePortalIndexes[j]);
			fprintf(rdxFile, "};\n");

			
			fprintf(rdxFile, "public anonymous resource int const[] zone%i_leafs = new int const[]\n", i);
			fprintf(rdxFile, "{\n");
			for(unsigned int j=0;j<_leafs.Count();j++)
				if(_leafs[j].zone == i)
					fprintf(rdxFile, "    %i,\n", j);
			fprintf(rdxFile, "};\n");
		}
		
		fprintf(rdxFile, "public anonymous resource WorldModel.Zone const[] zones = new WorldModel.Zone const[]\n");
		fprintf(rdxFile, "{\n");
		for(unsigned int i=0;i<_zones.Count();i++)
		{
			const BSPZone &zone = _zones[i];
			
			fprintf(rdxFile, "    new WorldModel.Zone {\n");
			fprintf(rdxFile, "        reversePortals = %s,\n", zone.reversePortals ? "true" : "false" );
			fprintf(rdxFile, "        portalIndexes = zone%i_portalIndexes,\n", i);
			fprintf(rdxFile, "        leafs = zone%i_leafs,\n", i);
			fprintf(rdxFile, "    },\n");
		}
		fprintf(rdxFile, "};\n");
		
		for(unsigned int i=0;i<_zonePortals.Count();i++)
		{
			const BSPZonePortal &zonePortal = _zonePortals[i];
			
			fprintf(rdxFile, "public anonymous resource Vec3 const[] zonePortal%i_points = new Vec3 const[]\n", i);
			fprintf(rdxFile, "{\n");
			for(unsigned int j=0;j<zonePortal.windingPoints.Count();j++)
				fprintf(rdxFile, "    %s,\n", EncVector(zonePortal.windingPoints[j], 3, true));
			fprintf(rdxFile, "};\n");
		}
		
		fprintf(rdxFile, "public anonymous resource WorldModel.ZonePortal const[] zonePortals = new WorldModel.ZonePortal const[]\n");
		fprintf(rdxFile, "{\n");
		for(unsigned int i=0;i<_zonePortals.Count();i++)
		{
			const BSPZonePortal &zonePortal = _zonePortals[i];
			
			fprintf(rdxFile, "    new WorldModel.ZonePortal {\n");
			fprintf(rdxFile, "        planeIndex = %i,\n", zonePortal.planeIndex);
			fprintf(rdxFile, "        zone1 = %i,\n", zonePortal.zones[0]);
			fprintf(rdxFile, "        zone2 = %i,\n", zonePortal.zones[1]);
			fprintf(rdxFile, "        windingPoints = zonePortal%i_points,\n", i);
			fprintf(rdxFile, "    },\n");
		}
		fprintf(rdxFile, "};\n");
		
		fprintf(rdxFile, "}\n");	// GeometryData
		fprintf(rdxFile, "}\n");	// mapname
		fprintf(rdxFile, "}\n");	// Maps
		fprintf(rdxFile, "}\n");	// Resources
		fprintf(rdxFile, "}\n");	// TDP
	}
};

using namespace TDP;

int main(int argc, const char **argv)
{
	if(argc != 2)
	{
		fprintf(stderr, "bsp2tmrf <map name>");
		exit(-1);
	}

	CString name;
	name.Set(NULL, argv[1]);

	BSP2TMRF::CompiledBSPAsset ma;
	{
		FILE *bspFile;
		{
			CString bspName;
			bspName.SetConcat(NULL, "maps/", name);
			bspName.Append(NULL, ".bsp");
			bspFile = fopen(bspName.Chars(), "rb");
			printf("Input BSP:       %s\n", bspName.Chars());
		}

		ma.Load(bspFile, name);
		fclose(bspFile);
	}

	{
		FILE *rdxFile;
		FILE *geomFile;

		{
			// Generate project file
			CString bspName;
			bspName.SetConcat(NULL, "maps/", name);
			bspName.Append(NULL, ".rdxproj");

			printf("Project file:    %s\n", bspName.Chars());

			FILE *f = fopen(bspName.Chars(), "wb");
			fprintf(f, "local projectInfo =\n");
			fprintf(f, "{\n");
			fprintf(f, "\toutputFile = \"rdxclasses/TDP/Resources/Maps/%s.rxt\",\n", name.Chars());
			fprintf(f, "\tfiles =\n");
			fprintf(f, "\t{\n");
			fprintf(f, "\t\t\"maps/%s.resources.rdx\",\n", name.Chars());
			fprintf(f, "\t\t\"maps/%s.data.rdx\",\n", name.Chars());
			fprintf(f, "\t},\n");
			fprintf(f, "\tnamespaces =\n");
			fprintf(f, "\t{\n");
			fprintf(f, "\t\t\"TDP.Resources.Maps.%s\",\n", name.Chars());
			fprintf(f, "\t},\n");
			fprintf(f, "}\n");
			fprintf(f, "importproject(projectInfo, \"rdxsrc/Core.rdxproj\")\n");
			fprintf(f, "importproject(projectInfo, \"rdxsrc/TDP/Math.rdxproj\")\n");
			fprintf(f, "importproject(projectInfo, \"rdxsrc/TDP/Resources.rdxproj\")\n");
			fprintf(f, "importproject(projectInfo, \"materials/shaders/AllShaders.rdxproj\")\n");

			fprintf(f, "return projectInfo\n");

		}

		{
			// Clear out material cache file
			CString bspName;
			bspName.SetConcat(NULL, "maps/", name);
			bspName.Append(NULL, ".data.rdx");

			printf("Structural data: %s\n", bspName.Chars());
			rdxFile = fopen(bspName.Chars(), "wb");

			bspName.SetConcat(NULL, "maps/", name);
			bspName.Append(NULL, ".geom");
			printf("Geometric data:  %s\n", bspName.Chars());
			geomFile = fopen(bspName.Chars(), "wb");
		}
		
		ma.ExportRDX(name.Chars(), geomFile, rdxFile);
		fclose(geomFile);
		fclose(rdxFile);
		/*
		{
			terString bspName = terString("rdxclasses/TDP/Resources/Maps/") + shortName + ".rxt";
			rdxtFile = fopen(bspName.CStr(), "wb");
		}
		ma.ExportRDXT(shortName, rdxtFile);
		*/
	}

	/*
	{
		FILE *brushFile;
		{
			CString bspName = name + "_solids.tb";
			brushFile = fopen(bspName.Chars(), "wb");
		}

		ma.ExportSolids(brushFile);
		fclose(brushFile);
	}
	*/

	{
		FILE *materialJobFile;
		{
			CString mjName;
			mjName.SetConcat(NULL, "maps/", name);
			mjName.Append(NULL, ".jobs.lua");

			printf("Compile jobs:    %s\n", mjName.Chars());
			materialJobFile = fopen(mjName.Chars(), "wb");
		}
		ma.ExportMaterialJobs(materialJobFile, name.Chars());
		fclose(materialJobFile);
	}

	return 0;
}
